package de.herberlin.helpsystem;

import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import javax.swing.tree.DefaultMutableTreeNode;

/**
 * Builds and holds the Tree for Helpsystem tree
 * @author Hans Joachim Herbertz
 */
public class TreeRoot extends DefaultMutableTreeNode implements Serializable {

	/**
	 * Inner class defined here returns only 
	 * html, htm and directories. */
	private HTMLFilenameFilter htmlFilter = new HTMLFilenameFilter();

	/**
	 * Index on indexName->TreeNodeObject for smart access to
	 * TreeNodeObjects*/
	private Map index = new HashMap();
	/**
	 * Parent comes from DefaultMutableTreeNode
	 * must be overwritten to indicate that this 
	 * is the root. */
	protected DefaultMutableTreeNode parent = null;

	/**
	 * Root of the helpsystem must be stored for 
	 * file of TreeNodeObject (user object) might be overwritten
	 * with index.html file. */
	public static File root = new File(System.getProperty("user.dir"), "help");
	/**
	 * Constructor. Builds tree and parses files for 
	 * HTML - title
	 * @param helpLocationName is 
	 * 	- 	a directory name relative to current directory
	 * 	- 	the name of a zip file helplLocationName+".zip"
	 * 		in the current directory
	 *  -	or a "jar"-file in the current directory
	 * @throws HelpSystemException
	 */
	public TreeRoot(String helpLocationName) throws HelpSystemException {

		try {
			if ((root =
				new File(System.getProperty("user.dir"), helpLocationName))
				.isDirectory()) {
				// directory
				setUserObject(new TreeNodeObject(root));
				parseDirectory(this);
			} else if (
				(root =
					new File(
						System.getProperty("user.dir"),
						helpLocationName + ".zip"))
					.isFile()) {
				// zip file
				setUserObject(new TreeNodeObject(root));
				parseArchive(new FileInputStream(root));
			} else if (
				(root =
					new File(
						System.getProperty("user.dir"),
						helpLocationName + ".jar"))
					.isFile()) {
				// zip file
				setUserObject(new TreeNodeObject(root));
				parseArchive(new FileInputStream(root));

			} else {
				throw new Exception(
					"HelpLocationName: "
						+ helpLocationName
						+ " must point to an archive or a directory.");
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new HelpSystemException(e);
		}

		System.out.println("Root is: " + root);

	}

	private void parseArchive(InputStream zipIn) throws Exception {
		ZipInputStream in = null;
		try {
			in = new ZipInputStream(zipIn);
			ZipEntry entry = null;
			while ((entry = (ZipEntry) in.getNextEntry()) != null) {

				if (entry.isDirectory())
					continue;
				if (!entry.getName().matches(".*\\.html?"))
					continue;

				// create data
				URL url =
					new URL("jar:" + root.toURL() + "!/" + entry.getName());
				TreeNodeObject userObject = new TreeNodeObject(url);
				DefaultMutableTreeNode node =
					new DefaultMutableTreeNode(userObject);
				String path = urlToIndex(url);

				// ad to index map
				index.put(path, node);

				// build tree
				String[] segments = path.split("/");
				DefaultMutableTreeNode currentNode = this;
				for (int i = 1; i < segments.length - 1; i++) {
					DefaultMutableTreeNode newChild = null;
					Enumeration enu= currentNode.children();
					while (enu.hasMoreElements()) {
						DefaultMutableTreeNode child =
							(DefaultMutableTreeNode) enu.nextElement();
						if (segments[i].equals(child.getUserObject())) {
							newChild = child;
							break;
						}
					}
					if (newChild == null) {
						newChild = new DefaultMutableTreeNode(segments[i]);
						currentNode.add(newChild);
					}
					currentNode = newChild;
				}
				if (url.toString().matches(".*index\\.html?")) {
					// index.html is directory index
					currentNode.setUserObject(node.getUserObject());
				} else {
					// other files are children
					currentNode.add(node);
				}
			}
		} finally {
			if (in != null) {
				in.close();
			}
		}
	}
	/**
	 * Recursive parser for directories.
	 * Builds hierarchy of TreeNodes and fills Map index. 
	 */
	private void parseDirectory(DefaultMutableTreeNode aNode)
		throws Exception {

		File[] files =
			((TreeNodeObject) aNode.getUserObject()).getUrlAsFile().listFiles(
				htmlFilter);

		// removing node if no children by setting parent to null
		if (files.length == 0) {
			((DefaultMutableTreeNode) aNode.getParent()).remove(aNode);
			return;
		}

		// sort files 
		Arrays.sort(files);
		// adding children to node
		for (int i = 0; i < files.length; i++) {
			DefaultMutableTreeNode current =
				new DefaultMutableTreeNode(new TreeNodeObject(files[i]));
			index.put(urlToIndex(files[i].toURL()), current);
			if (files[i].getName().indexOf("index.htm") == -1) {
				// not an index.html
				aNode.add(current);
			} else {
				// Index file used for parent node
				// aNode.add(current);
				aNode.setUserObject(current.getUserObject());
			}
			if (files[i].isDirectory()) {
				parseDirectory(current);
			}
		}
	}

	/**
	 * Returns tree node for a given indexName. 
	 * The returned DefaultMutableTreeNode has a userObject of type
	 * TreeNodeObject that holds all information on the helppage. */
	public DefaultMutableTreeNode getChildForIndex(String key) {
		return (DefaultMutableTreeNode) index.get(key);
	}

	/**
	 * Builds an indexEntry for display(..) from an url.
	 * If File is /helpsystem.home/directory/file.htm
	 * the indexName will be /directory/file
	 *  */
	public static String urlToIndex(URL url) {
		String context = null;
		if (root != null) {
			try {
				if ("file".equals(url.getProtocol())) {
					context =
						url.getPath().substring(
							root.toURL().getPath().length());
				} else if ("jar".equals(url.getProtocol())) {
					context =
						url.toString().substring(
							url.toString().lastIndexOf("!/") + 2);
				}
			} catch (Throwable t) {
				t.printStackTrace();
			}
		}
		if (context == null) {
			context = url.getPath();
		}

		int html = context.indexOf(".htm");
		if (html > 0) {
			context = context.substring(0, html);
		}
		if (url.getRef() == null) {
			context = "/" + context;
		} else {
			context = "/" + context + "#" + url.getRef().trim();
		}
		return context;
	}

	/**
	 * Parses the build directory tree of html files 
	 * for title tag. Title is stored to TreeNodeObject. 
	 * */
	public void parseTitles() {

		Iterator it = index.keySet().iterator();
		while (it.hasNext()) {

			try {

				TreeNodeObject tno =
					(TreeNodeObject) ((DefaultMutableTreeNode) index
						.get((String) it.next()))
						.getUserObject();
				BufferedReader in =
					new BufferedReader(
						new InputStreamReader(tno.getUrl().openStream()));
				StringBuffer targetTitle = new StringBuffer();
				boolean startTagFound = false;
				String t = null;
				while ((t = in.readLine()) != null) {
					int begin = t.indexOf("<title>");
					if (begin == -1)
						begin = t.indexOf("<TITLE>");
					if (begin == -1 && !startTagFound)
						continue;
					startTagFound = true;

					int end = t.indexOf("</title>");
					if (end == -1)
						end = t.indexOf("</TITLE>");
					if (begin + 7 < end) {
						// start and end in one line
						tno.setTitle(t.substring(begin + 7, end).trim());
						break;
					} else if (end == -1 && begin > -1) {
						// start found but end tag not yet found
						// this is the line where <title> is
						targetTitle.append(t.substring(begin + 7).trim() + " ");
					} else if (end == -1 && begin == -1) {
						// start found but end tag not yet found
						// this is a line where no <title> is
						targetTitle.append(t.trim() + " ");
					} else if (begin == -1 && end > -1) {
						// end tag found but no begin tag at this line
						targetTitle.append(t.substring(0, end).trim());
						tno.setTitle(targetTitle.toString().trim());
					}
				} //~ in.readLine
				in.close();
				Thread.yield();
			} catch (Throwable t) {
				// silent fail; 
				// no title available if fails
				t.printStackTrace();
			}
		} //~ it.hasNext

	}

	/**
	* Filters html and htm files.
	*/
	class HTMLFilenameFilter implements FilenameFilter {
		public boolean accept(File dir, String name) {
			if (new File(dir, name).isDirectory()) {
				return true;
			}
			if (name.endsWith(".html") || name.endsWith(".htm")) {
				return true;
			} else {
				return false;
			}
		}
	}

}
